1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module tools.insert_resources_uwp;
12 import std.conv:to;
13 import std.process;
14 import std.string;
15 import std.algorithm:countUntil, filter;
16 import std.file;
17 import std.array;
18 import commons;
19 
20 enum vcxItemTypes = 
21 [
22     ///Images
23     ".png" : "Image",
24     ".tiff": "Image",
25     ".jpeg": "Image",
26     ".jpg" : "Image",
27     ".bmp" : "Image",
28     ".webp": "Image",
29     ///Binary
30     ".ttf" : "Document",
31     ".dll" : "Document",
32     ///Text
33     ".txt" : "Text",
34     ".fnt" : "Text",
35     ".conf": "Text",
36     ".ini" : "Text",
37     ".json": "Text",
38     ".tsx" : "Text",
39     ".tmx" : "Text",
40     ".xml" : "Text",
41     ///Audio
42     ".wav" : "Media",
43     ".mp3" : "Media",
44     ".ogg" : "Media"
45 ];
46 
47 
48 enum specialLabel = "<ItemGroup Label=\"ResourceCopy\">";
49 enum wordToFind = "</ItemGroup>";
50 
51 
52 /**
53  *
54  * Params:
55  *   t = Terminal for printing
56  *   fileName = The file name
57  * Returns: The type from the vcxItemTypes
58  */
59 string getType(ref Terminal t, string fileName)
60 {
61     string type = fileName.extension;
62     string* uwpType = (type in vcxItemTypes);
63     if(uwpType == null)
64     {
65         t.writelnHighlighted("File named ", fileName, " is an unrecognized type, returning as None");
66         return "None";
67     }
68     return *uwpType;
69 }
70 
71 
72 bool stripProject(ref Terminal t, ref string res, string fileName)
73 {
74     long start = res.countUntil(specialLabel);
75     if(start == -1)
76         return true;
77 
78     size_t itemGroupDepth = 0;
79     for(size_t i = start+specialLabel.length; i < res.length; i++)
80     {
81         size_t nextItemGroup = res[i..$].countUntil("<ItemGroup>");
82         size_t nextItemGroupEnd = res[i..$].countUntil("</ItemGroup>");
83 
84 
85         if(nextItemGroupEnd == -1 || nextItemGroup == -1)
86             throw new Exception("Could not find tokens on file "~fileName);
87 
88         if(nextItemGroupEnd < nextItemGroup)
89         {
90             i+= nextItemGroupEnd + "</ItemGroup>".length;
91             if(itemGroupDepth == 0)
92             {
93                 res = res[0..cast(size_t)start] ~ res[i..$];
94                 return true;
95             }
96             itemGroupDepth--;
97         }
98         else if(nextItemGroup < nextItemGroupEnd)
99         {
100             i+= nextItemGroup + "<ItemGroup>".length;
101             itemGroupDepth++;
102         }
103     }
104     return false;
105 }
106 
107 string getResourceDescriptor(ref Terminal t, DirEntry e, string targetPath)
108 {
109     string n = "$(ProjectDir)\\UWPResources"~e.name[targetPath.length..$];
110     return ("<"~getType(t, e.name) ~" Include=\""~ n ~ "\"/>");
111 }
112 
113 string getResourceFilterDescriptor(ref Terminal t, DirEntry e, string targetPath)
114 {
115     string type = getType(t, e.name);
116     string filterName = e.name[targetPath.length-"UWPResources\\".length..$];
117 
118     long ind = lastIndexOf(filterName, '\\');
119     if(ind == -1)
120     {
121         t.writelnError("Unexpected Error: Resource is a directory.");
122         return "";
123     }
124     filterName = filterName[0..cast(uint)ind];
125     if(filterName[0] == '\\')filterName = filterName[1..$];
126 
127     string n = "$(ProjectDir)\\UWPResources"~e.name[targetPath.length..$];
128 
129     return ("<"~type ~" Include=\""~ n ~ "\">\n\t<Filter>"~filterName~"</Filter>\n</"~type~">");
130 }
131 
132 string getFilterName(DirEntry e, string targetPath)
133 {
134     string descriptor = e.name[targetPath.length-"UWPResources\\".length..$];
135     long ind = lastIndexOf(descriptor, '\\');
136     descriptor = descriptor[0..cast(uint)ind];
137     if(descriptor[0] == '\\')descriptor = descriptor[1..$];
138     return descriptor;
139 }
140 
141 
142 string generateFilterDefinition(string[] filters)
143 {
144     string retVal = "";
145     foreach(f; filters)
146         retVal~="<Filter Include=\""~f~"\"/>\n";
147     return retVal[0..$-1];
148 }
149 
150 bool hasFilter(string filterName, const string[] filters)
151 {
152     for(int i = 0; i < filters.length; i++)
153         if(filters[i] == filterName)
154             return true;
155     return false;
156 }
157 
158 
159 void generateFilters(string filterName, ref string[] filters)
160 {
161     string[] paths = pathSplitter(filterName).array;
162     for(int i = 0; i < paths.length; i++)
163     {
164         string toGenerate = "";
165         for(int z = i; z >= 0; z--)
166             toGenerate = paths[z]~"\\"~toGenerate;
167         if(toGenerate[$-1] == '\\')
168             toGenerate = toGenerate[0..$-1];
169         if(!hasFilter(toGenerate, filters))
170             filters~= toGenerate;
171     }
172 }
173 
174 
175 bool stripUWPResources(ref Terminal t, ref string vcx, ref string vcxfilter)
176 {
177     if(!stripProject(t, vcx, ".vcxproj"))
178     {
179         t.writelnError(`Could not strip last resource. `);
180         return false;
181     }
182     if(!stripProject(t, vcxfilter,".vcxproj.filters"))
183     {
184         t.writelnError(`Could not strip last resource filters.`);
185         return false;
186     }
187     return true;
188 }
189 
190 bool insertUWPResources(ref Terminal t, string uwpVcxProjPath, string assetsToImport)
191 {
192     import std.path;
193     import tools.copyresources;
194     string vcxPath = buildNormalizedPath(uwpVcxProjPath, baseName(uwpVcxProjPath)~".vcxproj");
195     if(!exists(vcxPath))
196     {
197         t.writelnError(vcxPath, " does not exists.");
198         return false;
199     }
200     if(!exists(assetsToImport))
201     {
202         t.writelnError(assetsToImport, " does not exists. ");
203         return false;
204     }
205     string targetPath = buildNormalizedPath(uwpVcxProjPath, "UWPResources");
206     copyResources(t, assetsToImport, targetPath, false);
207 
208     string vcx = to!string(readText(vcxPath));
209     string vcxfilter = to!string(readText(vcxPath~".filters"));
210 
211 
212     if(!stripUWPResources(t, vcx, vcxfilter))
213     {
214         t.writelnError("Could not strip UWPResources '"~specialLabel~'\'');
215         return false;
216     }
217 
218     long startIndex       = cast(int)vcx.countUntil(wordToFind);
219     long startIndexFilter = cast(int)vcxfilter.countUntil(wordToFind);
220     if(startIndex == -1 || startIndexFilter == -1)
221     {
222         t.writelnError("File format is not yet supported.");
223         return false;
224     }
225 
226     startIndex      +=wordToFind.length;
227     startIndexFilter+=wordToFind.length;
228 
229 
230     string toAppend = specialLabel;
231     string toAppendFilter = "";
232 
233     string[] filters;
234 
235 
236     foreach(DirEntry e; dirEntries(targetPath, SpanMode.depth).filter!((DirEntry entry) => entry.isFile))
237     {
238         string resource = getResourceDescriptor(t, e, targetPath);
239         string filter = getResourceFilterDescriptor(t, e, targetPath);
240         if(resource == "")
241         {
242             t.writelnError("Fatal Error. Stopping resource insertion process.");
243             return false;
244         }
245         string filterDef = getFilterName(e, targetPath);
246 
247         if(!hasFilter(filterDef, filters))
248             generateFilters(filterDef, filters);
249 
250         toAppend~= resource~"\n";
251         toAppendFilter ~= filter~"\n";
252     }
253 
254     toAppend~= "</ItemGroup>";
255     toAppendFilter = specialLabel~generateFilterDefinition(filters)~"\n"~toAppendFilter~"</ItemGroup>";
256 
257     long plusIndex = vcx.countUntil('<');
258     if(plusIndex != 0) plusIndex--;
259 
260     long plusIndexFilter = vcxfilter.countUntil('<');
261     if(plusIndexFilter != 0) plusIndexFilter--;
262 
263     //Add files to the project
264     vcx =
265         vcx[plusIndex..cast(uint)startIndex+plusIndex]~
266         toAppend~
267         vcx[cast(uint)startIndex+plusIndex..$];
268 
269     //Add filters to the project
270     vcxfilter =
271         vcxfilter[plusIndexFilter..cast(uint)startIndexFilter+plusIndexFilter]~
272         toAppendFilter~
273         vcxfilter[cast(uint)startIndexFilter+plusIndexFilter..$];
274 
275 
276     t.writelnSuccess("Writing .vcxproj and .vcxproj.filters");
277     std.file.write(vcxPath, vcx);
278     std.file.write(vcxPath~".filters", vcxfilter);
279 
280     return true;
281 }